// [file: CodeGenerator.g, started: 04-06-2012]
//
// Code Generator for the Basic Expression language
// CodeGenerator.g: Code Generator outputting to the file obj.tasm
//
// @author   Keith Davelaar
// @version  2012.06.24

tree grammar CodeGenerator;

options {
	language = Java;
	tokenVocab=Compiler;               // Import tokens from Superhero.tokens
	ASTLabelType=CommonTree;            // AST nodes are of type CommonTree
}

@header{
	import entries.*;
}

@members{	
	//CodeWriter that writes codes
	CodeWriter writer;
	
	/** The next address to be used */
	private int nextAddr = 0;
	/** The next label to be used */
	private int nextLbl = 0;	
	
	//For error handling
    private List<RecognitionException> exceptions = new ArrayList<RecognitionException>();
    
    public List<RecognitionException> getExceptions(){ return exceptions; }
}

@rulecatch { 
    //Only IOExceptions, but otherwise the generated file complains
    catch (Exception ioe) { 
        ioe.printStackTrace();
    } 
}

// {{{--CodeGenerator rules---}}} \\
program
	:	^(BEGIN { writer = new CodeWriter(); } (statement)+ { writer.endProgram(); })
    ;

statement
	:	declaration
	|	procedure
	|	function
	|	expression { writer.expression(); }
	;
	
declaration
	:	^(CD1 id=IDENTIFIER {writer.free($id.text, nextAddr);} op=operand) { writer.constantDeclaration($id.text, $op.type, nextAddr++);}
    |	^(VD1 id=IDENTIFIER type=(INTDEF|CHARDEF|BOOLDEF)) { writer.variableDeclaration($id.text, $type.text, nextAddr++); }
    ;
    
procedure
	:	^(PROC1 { 
		int l1 = nextLbl++;
		int l2 = nextLbl++;
		List<String> pars = new ArrayList<String>(); 
		}		
		id=IDENTIFIER (par=parameter { pars.add($par.identifier); } )* 	{ writer.procedure_1($id.text, pars, l1, l2); }
		pc=p_compound { writer.procedure_2($id.text, pars.size(), $pc.stats, l2); nextAddr-=$pc.decls;})
	;

p_compound returns [int decls, int stats]
	:	^(LCURLY { $stats = 0; $decls = 0;} ((declaration{$decls++;}|expression){ $stats++;})+)
	;
    
function
	:	^(FUNC1 { 
		int l1 = nextLbl++;
		int l2 = nextLbl++;
		List<String> pars = new ArrayList<String>(); 
		}
		id=IDENTIFIER (par=parameter{ pars.add($par.identifier); } )* 
		type=(INTDEF|CHARDEF|BOOLDEF) { writer.function_1($id.text, $type.text, pars, l1, l2); }
		fc=f_compound { writer.function_2(pars.size(), $fc.stats, l1); nextAddr-=$fc.decls;})
	;
	
f_compound returns [int decls, int stats]
    : 	^(LCURLY { $stats = 0; $decls = 0;} ((declaration{$decls++;}|expression){ $stats++;})* RETURN expression )
    ;
	 
parameter returns [String identifier]
	:	id=IDENTIFIER type=(INTDEF|CHARDEF|BOOLDEF|ARRDEF)
		{
			$identifier = $id.text;
			writer.parameter($id.text, $type.text);
		}
	;

expression returns [char type]
	:	as=assignment {$type = $as.type;}
	|	ar=arithmetic {$type = $ar.type;}
	;

assignment returns [char type] 
    :	^((AE1|AE2) id=IDENTIFIER expression)
    	{
    		$type = writer.getType($id.text);
    		writer.assignment($id.text);
    	}
    ;

arithmetic returns [char type] 
    :	^(NOT operand) 
    	{	
    		$type = Entry.BOOLEAN;
    		writer.not();
    	}
    | 	^(PLUS expression (expr=expression)?) 
    	{	
    		$type = Entry.INTEGER;
    		if($expr.start != null){
    			writer.addition();
    		}
    	}
    | 	^(MINUS expression expr=expression?) 
    	{	
    		$type = Entry.INTEGER;
    		if($expr.start != null){
    			writer.subtraction();
    		}else{
    			writer.negation();
    		}
    	}
    | 	^(MULT expression expression)
    	{
    		$type = Entry.INTEGER;
    		writer.multiplication();
   		}
    | 	^(DIV expression expression) 
    	{
    		$type = Entry.INTEGER;
    		writer.divide(nextLbl++, nextLbl++, nextAddr++);
   		}
    | 	^(MOD expression expression)
    	{
    		$type = Entry.INTEGER;
    		writer.modulo(nextLbl++, nextLbl++, nextAddr++);
   		}
    | 	^(OR expression expression)
    	{
    		$type = Entry.BOOLEAN;
    		writer.or();
   		}
    | 	^(AND expression expression)
    	{
    		$type = Entry.BOOLEAN;
    		writer.and();
   		}
    | 	^(LT expression expression)
    	{
    		$type = Entry.BOOLEAN;
    		writer.lessThan();
   		}
    | 	^(LE expression expression) 
    	{
    		$type = Entry.BOOLEAN;
    		writer.lessEqual();
   		}
    | 	^(GT expression expression) 
    	{
    		$type = Entry.BOOLEAN;
    		writer.greaterThan();
   		}
    | 	^(GE expression expression) 
    	{
    		$type = Entry.BOOLEAN;
    		writer.greaterEqual();
   		}
    | 	^(EQ expression expression)
    	{
    		$type = Entry.BOOLEAN;
    		writer.equals();
   		}
    	
    |	^(NE expression expression)
    	{
    		$type = Entry.BOOLEAN;
    		writer.notEquals();
   		}
    | 	op=operand {$type = $op.type;}
    ;

operand returns [char type] 
    : 	id=IDENTIFIER
    	{
   			$type = writer.getType($id.text);
   			writer.identifier($id.text);
    	}
    | 	n=INTEGER
    	{
    		$type = Entry.INTEGER;
    		writer.integer($n.text);
    	}
    | 	TRUE
    	{
    		$type = Entry.BOOLEAN;
    		writer.boolean_true();
    	}
    | 	FALSE
    	{
    		$type = Entry.BOOLEAN;
    		writer.boolean_false();
    	}
    |	c=CHARACTER
    	{
    		$type = Entry.CHARACTER;
    		writer.character($c.text);
    	}
    |	ce=compound
    	{
    		$type = $ce.type;
    		writer.compound($ce.stats);	
    		nextAddr-=$ce.decls;	
    	}
	|	re=read {$type = $re.type;}
	|	pe=print {$type = $pe.type;}
	|	ie=conditional {$type = $ie.type; writer.compound($ie.stats); }
	|	we=dowhile {$type = $we.type; writer.compound($we.stats); }
	|	fc=funcCall {$type=$fc.type;}
    |	pc=procCall {$type=$pc.type;}
	;
   
compound returns [int stats, char type, int decls] 
	:	^((CE1|LCURLY)
	 	{
	 		$stats = -1;
	 		$decls = 0;
	 	}
	 	((ex=expression|declaration{$decls++;}) {$stats++;})+
		{
			$type = $ex.type;
		})
    ;
 
read returns [char type = Entry.VOID] 
	:	^(RE (id+=IDENTIFIER)+) { $type = writer.read($id); }
    ;
    
print returns [char type]
    : 	^(PE1 { int cnt = 1; } 
    	(ex=expression { $type=writer.print(cnt++, $ex.type); } )+)
    ;
    
conditional  returns [int stats, char type] 
    : 	^(IE ce1=compound
    	{
    		int l1 = nextLbl++;
    		int l2 = nextLbl++;
    		writer.conditional_1(l1);
    	}
    	ce2=compound 
    	{
    		writer.conditional_2($ce2.stats, l1, l2);	
    	}
    	ce3=compound? 
    	{
    		if($ce2.type == $ce3.type){
    			$type = $ce2.type;
    		} else{
    			$type = Entry.VOID;
    		}
   			if(ce3 == null){
   				writer.conditional_4(l2);
   			}else{
   				writer.conditional_3($ce3.stats, l2);
   			}
   			$stats = $ce1.stats;
   			nextAddr = nextAddr - $ce1.decls - $ce2.decls - $ce3.decls;
    	})
    ;
    
dowhile returns [char type = Entry.VOID, int stats] 
    : 	^(WE 
    	{    		
    		int l1 = nextLbl++;
    		int l2 = nextLbl++;
    		writer.dowhile_1(l1);
    	}
    	ce1=compound 
    	{
    		writer.dowhile_2($ce1.stats, l2);
    	}
    	ce2=compound)
    	{
    		writer.dowhile_3($ce2.stats+1, l1, l2);
    		nextAddr = nextAddr - $ce1.decls - $ce2.decls;
    		$stats = $ce1.stats;
    	}
    ;

procCall returns [char type=Entry.VOID]
	:	^(PC id=IDENTIFIER (expression)* 
		{ 
			writer.call($id.text);
		})
	;
	
funcCall returns [char type]
	:	^(FC id=IDENTIFIER {$type = writer.getFunctionType($id.text);} (expression)*
		{ 
			writer.call($id.text);
		})
	;
//E{CodeGenerator.g}